import json
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from keras.models import Sequential
from sklearn.model_selection import train_test_split
from keras.layers import TimeDistributed, LSTM, Dense, Dropout

cycling_datas = pd.read_csv('./data/hour.csv')
# print(cycling_datas.head(3))

# 数据的哑变量处理：就是one-hot热编码处理，将各种类别的数据内容转化成数字便于运算处理，并且是唯一的
# 如果值是True或者False表示，或者用枚举可以表示的值，我们就可以对该数据进行哑变量处理
dummy_fields = ['season', 'weathersit', 'mnth', 'hr', 'weekday']
for each in dummy_fields:
    # pd.get_dummies() 参数说明 ：
    # 1、输入的数据
    # data: array - like, Series, or DataFrame
    # 2、get_dummies转换后，列名的前缀
    # prefix: string, list of strings,
    # or dict of strings, default None
    # 5、获得k中的k - 1个类别值，去除第一个
    # drop_first: bool, default False
    dummies = pd.get_dummies(cycling_datas[each], prefix=each, drop_first=False)
    # print(dummies)

    # pd.concat参数说明
    # objs: series，dataframe或者是panel构成的序列lsit
    # axis： 需要合并链接的轴，0是行，1是列
    cycling_datas = pd.concat([cycling_datas, dummies], axis=1)
# print(cycling_datas.head(3))


# 删除无关数据字段
fields_to_drop = ['instant', 'season', 'weathersit', 'weekday',
                  'atemp', 'mnth', 'workingday', 'hr', 'dteday']
# 删除无关数据以及经过扩维的列axis=0表示删除指定行，axis=1表示删除指定列
cycling_datas = cycling_datas.drop(fields_to_drop, axis=1)

# 目标：预测未来使用单车的骑行人数，以及注册的用户和临时用户的预测
# 所以我们在训练模型时，骑行人数是我们要预测的target，剩下的都是features

# 缩放特征
scaled_features = {}
# 量化特征
quant_features = ['casual', 'registered', 'cnt', 'temp', 'hum', 'windspeed']
for each in quant_features:
    # 计算这几个字段的 均值 和 标准差
    mean, std = cycling_datas[each].mean(), cycling_datas[each].std()
    scaled_features[each] = [mean, std]
    # 数据减去均值除以标准差等于标准分值（Standard Score），这样处理是为了符合标准正态分布
    # print(cycling_datas.loc[:, each])
    # cycling_datas.loc[:, each] = (cycling_datas[each] - mean) / std
    cycling_datas[each] = (cycling_datas[each] - mean) / std
# print(scaled_features)

# 把特征字字段的均值和标准差保存为json文件
filename = './database/scaled_features.json'
with open(filename, 'w') as file_obj:
    json.dump(scaled_features, file_obj)

# 分开特征features和目标target
target_cols = ['cnt', 'casual', 'registered']
y_labels = cycling_datas[target_cols]
X_features = cycling_datas.drop(target_cols, axis=1)
# print(X_features.head(3))

# 设置随机数种子，保证每次分割的结果是一样的
# train_test_split参数说明：
#
# *arrays：可以是列表、numpy数组、scipy稀疏矩阵或pandas的数据框
#
# test_size：可以为浮点、整数或None，默认为None
#
# ①若为浮点时，表示测试集占总样本的百分比
#
# ②若为整数时，表示测试样本样本数
#
# ③若为None时，test size自动设置成0.25
#
# train_size：可以为浮点、整数或None，默认为None
#
# ①若为浮点时，表示训练集占总样本的百分比
#
# ②若为整数时，表示训练样本的样本数
#
# ③若为None时，train_size自动被设置成0.75
#
# random_state：可以为整数、RandomState实例或None，默认为None
#
# ①若为None时，每次生成的数据都是随机，可能不一样
#
# ②若为整数时，每次生成的数据都相同
#
# stratify：可以为类似数组或None
#
# ①若为None时，划分出来的测试集或训练集中，其类标签的比例也是随机的
#
# ②若不为None时，划分出来的测试集或训练集中，其类标签的比例同输入的数组中类标签的比例相同，可以用于处理不均衡的数据集
X_train, X_test, y_train, y_test = train_test_split(X_features,
                                                    y_labels,
                                                    test_size=0.2,
                                                    random_state=42)
# 获取训练集、测试集csv的表头
X_head = X_train.columns.values
y_head = y_train.columns.values

# 训练集转为数组
X_train = np.array(X_train)
y_train = np.array(y_train)

# 把测试集的一半数据用作验证集
test_size = int(X_test.shape[0] / 2)
# print(test_size)

# 测试集、验证集转为数组
X_valid = np.array(X_test[:test_size])
y_valid = np.array(y_test[:test_size])

X_test = np.array(X_test[test_size:])
y_test = np.array(y_test[test_size:])

# 将经过处理之后的训练集、测试集、验证集保存数据为csv文件
csv_file = [X_train, y_train, X_test, y_test, X_valid, y_valid]
csv_head = [X_head, y_head, X_head, y_head, X_head, y_head]
csv_file_name = ['X_train', 'y_train', 'X_test', 'y_test', 'X_valid', 'y_valid']
for i in range(len(csv_file)):
    df = pd.DataFrame(columns=csv_head[i], data=csv_file[i])
    df.to_csv('./database/{}.csv'.format(csv_file_name[i]), encoding='utf-8')

# 将三个数据集用numpy扩维，便于LSTM模型输入
X_train = np.expand_dims(X_train, axis=1)
y_train = np.expand_dims(y_train, axis=1)

X_valid = np.expand_dims(X_valid, axis=1)
y_valid = np.expand_dims(y_valid, axis=1)

X_test = np.expand_dims(X_test, axis=1)
y_test = np.expand_dims(y_test, axis=1)

# 显示三个数据集的类型
print("X_train.shape={}, y_train.shape={}.".format(X_train.shape, y_train.shape))
print("X_valid.shape={}, y_valid.shape={}.".format(X_valid.shape, y_valid.shape))
print("X_test.shape={}, y_test.shape={}.".format(X_test.shape, y_test.shape))

# 创建LSTM网络层
model = Sequential()  # 顺序模型
# 参数说明：
# units: 输出空间的维度
# input_shape: (timestep, input_dim),timestep可以设置为None,由输入决定,input_dime根据具体情况
# activation: 激活函数,默认tanh
# return_sequences: 布尔值。是否返回最后的输出。在输出序列或完整序列中。
model.add(LSTM(256, input_shape=(X_train.shape[1], X_train.shape[2]), return_sequences=True))
model.add(Dropout(0.4))  # 抛弃一些参数防止过拟合，X可以取0--1之间，代表百分比抛弃数据
model.add(LSTM(256, return_sequences=True))
model.add(Dropout(0.3))
model.add(LSTM(128, return_sequences=True))
model.add(LSTM(32, return_sequences=True))
# 这是一个基于时间维度的全连接层。主要就是用来构建RNN(递归神经网络)的，但是在构建RNN时需要设置return_sequences=True
# 参数：
# 1、output_dim: int >= 0，输出结果的维度
# 2、activation : 激活函数名称或者Theano function。可以使用Keras内置的，也可以是传递自己编写的Theano function。
# 如果不明确指定，那么将没有激活函数会被应用。
model.add(TimeDistributed(Dense(3, activation='linear')))
# 参数说明：
# 1、optimizer: 字符串（优化器名）或者优化器实例
# 2、loss: 字符串（目标函数名）或目标函数。 如果模型具有多个输出，则可以通过传递损失函数的字典或列表，
# 在每个输出上使用不同的损失。 模型将最小化的损失值将是所有单个损失的总和。
# 采用均方差作为损失函数，adam优化器
model.compile(loss='mse', optimizer='adam')

# 拟合网络
# 参数说明：
# 1、x: 训练数据的 Numpy 数组,或者是 Numpy 数组的列表
# 如果模型中的输入层被命名，你也可以传递一个字典，将输入层名称映射到 Numpy 数组。
# 2、y: 目标（标签）数据的 Numpy 数组（如果模型只有一个输出）， 或者是 Numpy 数组的列表（如果模型有多个输出）。
# 如果模型中的输出层被命名，你也可以传递一个字典，将输出层名称映射到 Numpy 数组。
# 3、batch_size: 整数或 None。每次梯度更新的样本数。如果未指定，默认为 32。
# 4、epochs: 整数。训练模型迭代轮次。一个轮次是在整个 x 和 y 上的一轮迭代。 请注意，与 initial_epoch 一起，epochs 被理解为
# 「最终轮次」。模型并不是训练了 epochs 轮，而是到第 epochs 轮停止训练。
# 5、verbose: 0, 1 或 2。日志显示模式。 0 = 安静模式, 1 = 进度条, 2 = 每轮一行。
# 6、validation_data: 元组 (x_val，y_val) 或元组 (x_val，y_val，val_sample_weights)， 用来评估损失，
# 以及在每轮结束时的任何模型度量指标。模型将不会在这个数据上进行训练。这个参数会覆盖 validation_split。

# 返回
# 一个 History 对象。其 History.history 属性是连续 epoch 训练损失和评估值，以及验证集损失和评估值的记录（如果适用）。
history = model.fit(X_train, y_train, epochs=50, batch_size=50, validation_data=(X_test, y_test), verbose=2)
# 根据fit后返回的history绘制loss--val_loss曲线
plt.plot(history.history['loss'], label='train')
plt.plot(history.history['val_loss'], label='test')
plt.legend()
plt.savefig("./result/模型损失函数图像.png")
plt.show()
# 保存训练模型
model.save("./result/model.h5")
